import json
import time
from datetime import datetime

import requests
from PyQt5 import QtGui
from PyQt5.QtCore import pyqtSlot, Qt, QTimer
from PyQt5.QtGui import QMovie
from PyQt5.QtWidgets import QMainWindow, QMessageBox, QFileDialog, QInputDialog, QApplication, QDialog, QMenu

from structures.basic_structure import *
from trajectories.traj_b_spline_curve import BSplineCurveTrajectory
from trajectories.traj_circle import CircleTrajectory
from trajectories.traj_free_drawing import FreeDrawingTrajectory
from trajectories.traj_line import LineTrajectory
from trajectories.traj_rect import RectTrajectory
from trajectories.traj_triangle import TriangleTrajectory
from trajectories.traj_walks import WalksTrajectory
from view.four_leg_window import FourLegWindow
from view.layers.shape_layer import Shape
from view.ui.ui_main_window import Ui_MainWindow
from view.widget.b_spline_dialog import BSplineDialog
from view.widget.shape_dialog import ShapeLineDialog


class MainWindow(QMainWindow):

    def __init__(self, mode=1):
        super().__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.setWindowTitle("五连杆模拟器 - 黑马程序员 - https://gitee.com/tangyang/leg-kinematics - v1.5")

        # 右侧面板
        self.ui.combo_box_mode.currentIndexChanged['int'].connect(self.on_mode_changed)
        self.ui.radio_end_left.clicked.connect(lambda e: self.ui.leg_widget.set_end_mode(True))
        self.ui.radio_end_right.clicked.connect(lambda e: self.ui.leg_widget.set_end_mode(False))
        self.ui.check_box_coordinate.stateChanged['int'].connect(self.ui.leg_widget.set_coordinate_visible)
        self.ui.btn_four_leg.clicked.connect(self.open_four_leg_window)

        self.ui.btn_walks.clicked.connect(self.start_walks)
        self.ui.btn_walks_clean.clicked.connect(lambda: self.ui.leg_widget.trajectory_clean(WalksTrajectory.TRACK_KEY))
        # self.ui.btn_traj_line.clicked.connect(lambda e: self.update_shape_draw(Shape.LINE))
        # self.ui.btn_traj_circle.clicked.connect(self.draw_circle)
        # self.ui.btn_traj_rect.clicked.connect(self.draw_rect)
        # self.ui.btn_traj_triangle.clicked.connect(self.draw_triangle)
        # self.ui.btn_traj_shape_exit.clicked.connect(lambda e: self.update_shape_draw(Shape.NONE))
        self.ui.leg_widget.set_shape_drawing_finished(self.on_shape_finished)
        self.update_shape_draw(Shape.NONE)

        self.ui.btn_traj_clean.clicked.connect(lambda checked: self.ui.leg_widget.trajectory_clean())

        self.ui.btn_traj_create.clicked.connect(self.on_traj_create)
        self.ui.btn_traj_exit.clicked.connect(self.on_traj_exit)
        self.ui.btn_traj_run.clicked.connect(self.on_traj_run)
        self.ui.btn_traj_run_2.clicked.connect(self.on_traj_run_2)
        self.ui.btn_traj_save.clicked.connect(self.on_traj_save)
        self.ui.btn_traj_load.clicked.connect(self.on_traj_load)
        self.ui.btn_traj_load_2.clicked.connect(self.on_traj_load_2)
        self.ui.btn_traj_path_save.clicked.connect(self.on_traj_path_save)
        self.ui.btn_traj_joints_save.clicked.connect(self.on_traj_joints_save)
        # self.ui.btn_traj_joints_save.setVisible(False)

        self.q_movie_dog = QMovie(":/res/dogleg.gif")
        self.q_movie_clock = QMovie(":/res/plotclock.gif")
        self.ui.img_gif.setMovie(self.q_movie_dog)
        self.q_movie_dog.start()

        self.update_free_action_state()

        # 鼠标坐标监听
        self.ui.leg_widget.mouse_tracking_event.connect(self.on_mouse_tracking_event)

        # 下侧面板-连杆
        self.init_all_link_spinbox()
        self.ui.btn_links_reset.clicked.connect(self.ui.leg_widget.reset_links)
        self.ui.leg_widget.structure_changed_event.connect(self.on_structure_changed)

        self.ui.check_ac.setEnabled(False)
        self.links_visible = {LINK_AB: self.ui.check_ab,
                              LINK_CD: self.ui.check_cd,
                              LINK_BE: self.ui.check_be,
                              LINK_DE: self.ui.check_de,
                              LINK_EF: self.ui.check_ef}
        structure = self.ui.leg_widget.get_structure()
        for link_name, check_box in self.links_visible.items():
            check_box.setChecked(structure.is_link_visible(link_name))
            check_box.stateChanged['int'].connect(lambda state, ln=link_name: self.on_link_visible_changed(ln, state))

        # 下侧面板-角度
        self.init_all_angle_spinbox()
        self.ui.spin_alpha.valueChanged['int'].connect(lambda v: self.on_degrees_changed(v, ANGLE_ALPHA))
        self.ui.spin_beta.valueChanged['int'].connect(lambda v: self.on_degrees_changed(v, ANGLE_BETA))
        self.ui.spin_theta.valueChanged['int'].connect(lambda v: self.on_degrees_changed(v, ANGLE_THETA))

        self.ui.leg_widget.setFocus()
        # self.ui.combo_box_mode.setFocus()

        if 1 <= mode <= 2:
            self.ui.combo_box_mode.setCurrentIndex(mode - 1)

        self.four_leg_window = None
        self.four_leg_window_ui = None

        self.ui.btn_connect.setVisible(False)
        self.ui.edit_remote_ip.setVisible(False)
        # self.ui.btn_connect.clicked.connect(self.on_remote_connect)

        # 开启线程每隔10秒判断一次
        # self.timer = QTimer()
        # self.timer.timeout.connect(self.on_timer_timeout)
        # self.timer.setInterval(10 * 1000)
        # self.timer.start()
        # self.on_timer_timeout()

        self.ui.btn_send.clicked.connect(self.on_send)

        self.ui.btn_remote_mode.clicked.connect(self.on_remote_mode)

        # 开启定时器，每一秒钟执行一次
        self.timer = QTimer()
        self.timer.timeout.connect(self.on_clock_timer_timeout)
        self.timer.setInterval(1000)
        self.timer.start()

    # 初始化画面右键菜单
    def contextMenuEvent(self, event: QtGui.QContextMenuEvent) -> None:
        # 如果当前是自定义路径编辑模式，则返回
        if self.ui.leg_widget.is_free_drawing():
            return
        pos = event.pos()
        widget_pos = self.ui.leg_widget.pos()
        widget_size = self.ui.leg_widget.size()

        # print(f"pos: {pos}")
        # print(widget_pos, widget_size)
        # 如果pos在widget内，则显示菜单
        if widget_pos.x() <= pos.x() <= widget_pos.x() + widget_size.width() and \
                widget_pos.y() <= pos.y() <= widget_pos.y() + widget_size.height():
            cmenu = QMenu(self)

            cmenu.addAction('直线', lambda : self.update_shape_draw(Shape.LINE))
            cmenu.addAction('圆形', lambda : self.draw_circle())
            cmenu.addAction('三角形', lambda : self.draw_triangle())
            cmenu.addAction('矩形', lambda : self.draw_rect())
            cmenu.addSeparator()
            cmenu.addAction('保存关节角', self.on_traj_joints_save)
            cmenu.addAction('保存路径点', self.on_traj_path_save)
            cmenu.addSeparator()
            cmenu.addAction('清除末尾路径点', self.ui.leg_widget.trajectory_clean)
            cmenu.exec_(event.globalPos())

    def on_clock_timer_timeout(self):
        # 获取当前 H:M:S 时间
        now = datetime.now()

        # 获取当前+8时区的时间戳
        # timestamp = int(time.mktime(now.timetuple()) + 8 * 60 * 60)
        # timestamp1 = int(time.time() + 8 * 60 * 60)
        # print(timestamp, timestamp1)

        time_str = now.strftime("%H:%M:%S")
        self.ui.label_current_time.setText(time_str)

    def on_remote_mode(self):
        host, path = "192.168.10.1", "plot/clock_auto"
        try:
            url = f"http://{host}/{path}"
            print(url)
            # 执行get请求并获取响应
            response = requests.get(url, params={"ts": int(time.time() + 8 * 60 * 60)}, timeout=1)
            self.ui.statusbar.showMessage("发送成功：{}".format(url))
            print(response.text)
            if response.text == "running":
                self.ui.btn_remote_mode.setText("自动时钟运行中...（停止）")
                self.ui.btn_remote_mode.setStyleSheet("background-color: rgb(255, 0, 0);")
            elif response.text == "stop":
                self.ui.btn_remote_mode.setText("运行-自动时钟")
                self.ui.btn_remote_mode.setStyleSheet("background-color: none;")
            else:
                self.ui.btn_remote_mode.setText("运行-自动时钟")
                self.ui.btn_remote_mode.setStyleSheet("background-color: none;")

        except Exception as e:
            print(e)
            self.ui.statusbar.showMessage("发送失败, 请检查{}是否可联通".format(host))
            self.ui.btn_remote_mode.setText("运行-自动时钟")
            self.ui.btn_remote_mode.setStyleSheet("background-color: none;")


    def on_send(self):
        # 获取 edit_data 组件的内容并通过requests执行post请求发送到 http://192.168.10.1/plot/clock
        # 发送的数据格式为: {"data": "xxxxx"}
        data = self.ui.edit_data.text()
        if data.strip() == "":
            return
        split_arr = data.split(":")
        if len(split_arr[0]) == 0:
            split_arr[0] = "00"
        elif len(split_arr[0]) == 1:
            split_arr[0] = "0" + split_arr[0]

        if len(split_arr[1]) == 0:
            split_arr[1] = "00"
        elif len(split_arr[1]) == 1:
            split_arr[1] = "0" + split_arr[1]

        rst_arr = split_arr[0] + ":" + split_arr[1]

        host, path = "192.168.10.1", "plot/clock"
        try:
            url = f"http://{host}/{path}"
            print(url)
            requests.get(url, params={"data": rst_arr}, timeout=1)
            self.ui.statusbar.showMessage("发送成功：{} data: {}".format(url, rst_arr))
        except Exception as e:
            print(e)
            self.ui.statusbar.showMessage("发送失败, 请检查{}是否可联通".format(host))


    def on_timer_timeout(self):
        # 使用requests判断当前设备与 remote_ip 是否可连通
        remote_ip = self.ui.edit_remote_ip.text()
        print("remote_ip:", remote_ip)
        if remote_ip.strip() == "":
            return
        try:
            # 超时时长为3秒
            requests.get(f"http://{remote_ip}/plot", timeout=3)
            self.ui.btn_connect.setText("已连接")
            self.ui.btn_connect.setEnabled(False)
        except:
            self.ui.btn_connect.setText("连接中...")
            self.ui.btn_connect.setEnabled(False)


    def on_remote_connect(self):
        if self.ui.btn_connect.text() == "连接":
            self.ui.btn_connect.setText("断开")
            self.ui.btn_connect.setStyleSheet("background-color: rgb(255, 0, 0);")


    def open_four_leg_window(self):
        print("open_four_leg_window")
        self.four_leg_window = FourLegWindow()
        self.four_leg_window.show()

    def start_walks(self):
        start = self.ui.spinBox_start.value()
        end = self.ui.spinBox_end.value()
        self.ui.leg_widget.trajectory_generate(WalksTrajectory.TRACK_KEY, start, end)

    def update_free_action_state(self):
        is_drawing = self.ui.leg_widget.is_free_drawing()

        # 所有状态：加载文件，加载json串

        # 非编辑状态： 创建
        self.ui.btn_traj_create.setVisible(not is_drawing)
        # self.ui.btn_traj_load.setVisible(not is_drawing)
        # 编辑状态： 运行、保存、退出
        self.ui.btn_traj_run.setVisible(is_drawing)
        self.ui.btn_traj_save.setVisible(is_drawing)
        self.ui.btn_traj_exit.setVisible(is_drawing)
        self.ui.btn_traj_run_2.setVisible(is_drawing)
        self.ui.listWidget.setVisible(is_drawing)

        # if is_drawing:
        #     self.ui.btn_traj_run.setFocus()
        # else:
        #     self.ui.btn_traj_create.setFocus()

    def eventFilter(self, obj, event):
        # 判断是否是Ctrl + H
        if event.type() == QEvent.KeyPress:
            if event.key() == Qt.Key_H and event.modifiers() == Qt.ControlModifier:
                print("Ctrl + H")
                return True
        return False

    def on_traj_create(self):
        self.ui.leg_widget.set_free_drawing(True)
        self.update_free_action_state()

    def on_traj_run(self):
        rst = self.ui.leg_widget.check_free_drawing_and_run()
        if not rst:
            # 警告用户运行失败
            QMessageBox.warning(self, "运行失败", "轨迹中包含不可达关键点，请检查轨迹是否正确！")

        self.ui.leg_widget.trajectory_generate(FreeDrawingTrajectory.TRACK_KEY,
                                               self.ui.leg_widget.get_final_free_drawing_points_list())

    def on_traj_run_2(self):

        # 弹窗输入自定义参数
        # s_arg, rst = QInputDialog.getItem(self, "B样条曲线-自定义参数", "请选择B-Spline的k参数（越小越接近原曲线）：",
        #                                   ["1", "2", "3", "4", "5"], current=2, editable=False)

        dialog = BSplineDialog(self)
        dialog_rst = dialog.exec_()

        if dialog_rst != QDialog.Accepted:
            return

        k, times = dialog.get_values()
        print(k, times)

        rst = self.ui.leg_widget.check_free_drawing_and_run()
        if not rst:
            # 警告用户运行失败
            QMessageBox.warning(self, "运行失败", "轨迹中包含不可达关键点，请检查轨迹是否正确！")
            return

        points_list = self.ui.leg_widget.get_final_free_drawing_points_list()

        # if len(points_list) > 1:
        #     QMessageBox.warning(self, "运行失败", "B样条曲线只支持单条轨迹！")
        #     return

        self.ui.leg_widget.trajectory_generate(BSplineCurveTrajectory.TRACK_KEY,
                                               int(k), times, points_list)

    def on_traj_save(self):
        points_list = self.ui.leg_widget.get_final_free_drawing_points_list(for_save=True)

        if points_list is None or len(points_list) == 0:
            self.ui.statusbar.showMessage("请先绘制轨迹", 3000)
            return

        self.open_and_save_json("轨迹", points_list)

    def open_and_save_json(self, title_str, save_data):
        # 打开文件创建对话框
        file_name, rst = QFileDialog.getSaveFileName(self, "保存%s" % title_str, "", "JSON Files (*.json)")
        if not rst or file_name == "":
            return
        # 保存json到文件
        with open(file_name, 'w') as f:
            # 将points_list转换为json格式，并保存到文件
            json.dump(save_data, f)

        print("保存{}成功！ {}".format(title_str, file_name))
        QMessageBox.information(self, "保存成功", f"{title_str}已保存至-> {file_name}")

    def on_traj_path_save(self):
        points = self.ui.leg_widget.get_traj_points(True)

        if points is None or len(points) == 0:
            QMessageBox.warning(self, "保存失败", "请先运行轨迹后保存！")
            return

        print(points)

        self.open_and_save_json("路径点", points)

    def on_traj_joints_save(self):
        joints = self.ui.leg_widget.get_traj_joints()
        if joints is None or len(joints) == 0:
            QMessageBox.warning(self, "保存失败", "请先运行轨迹后保存！")
            return

        self.open_and_save_json("关节角", joints)

    def on_traj_load_2(self):
        # 读取粘贴板内容
        clipboard_text = QApplication.clipboard().text()
        json_text, rst = QInputDialog.getText(self, "轨迹加载", "请输入轨迹JSON串：", text=clipboard_text)
        if not rst:
            return

        try:
            points_list = json.loads(json_text)
            if points_list is None or len(points_list) == 0:
                QMessageBox.information(self, "加载失败", "加载失败，请检查json是否正确：\n" + json_text)
                return
            self.ui.leg_widget.set_free_drawing(True, points_list)
            self.update_free_action_state()
        except Exception as e:
            print(e)
            QMessageBox.information(self, "加载失败", "加载失败，请检查json是否正确：\n" + json_text)

    @pyqtSlot(bool)
    def on_traj_load(self, event):
        print("on_traj_load: ", event)

        # 打开文件对话框
        file_name, _ = QFileDialog.getOpenFileName(self, "打开轨迹", "", "JSON Files (*.json)")
        if file_name == "":
            return

        # 读取json文件
        with open(file_name, 'r') as f:
            points_list = json.load(f)
            if points_list is None or len(points_list) == 0:
                QMessageBox.information(self, "加载失败", "加载失败，请检查文件是否正确：\n" + file_name)
                return

            self.ui.leg_widget.set_free_drawing(True, points_list)
            self.update_free_action_state()

    def on_traj_exit(self):
        question_answer = QMessageBox.question(self, '提示', '退出前记得保存轨迹到文件哦！\r\n确定要退出吗？',
                                               QMessageBox.Yes | QMessageBox.No,
                                               QMessageBox.No)
        if question_answer == QMessageBox.Yes:
            self.ui.leg_widget.set_free_drawing(False)
            self.update_free_action_state()

    def on_link_visible_changed(self, link_name, state):
        print("link {} visible changed to {}".format(link_name, state))
        self.ui.leg_widget.set_link_visible(link_name, state)

    def on_mode_changed(self, mode_index):
        print("mode changed to {}".format(mode_index))
        self.ui.leg_widget.set_mode(mode_index)
        self.init_all_angle_spinbox()
        self.init_all_link_spinbox(True)

        self.ui.btn_four_leg.setVisible(mode_index == 0)

        self.q_movie_dog.stop()
        self.q_movie_clock.stop()

        if mode_index == 0:
            q_movie = self.q_movie_dog
        elif mode_index == 1:
            q_movie = self.q_movie_clock
        else:
            return

        self.ui.img_gif.setMovie(q_movie)
        q_movie.start()

    def init_all_link_spinbox(self, just_update_value=False):
        self.init_link_spinbox(ORG_LINK_AC, self.ui.spin_ac, just_update_value)
        self.init_link_spinbox(LINK_AB, self.ui.spin_ab, just_update_value)
        self.init_link_spinbox(LINK_CD, self.ui.spin_cd, just_update_value)
        self.init_link_spinbox(LINK_BE, self.ui.spin_be, just_update_value)
        self.init_link_spinbox(LINK_DE, self.ui.spin_de, just_update_value)
        self.init_link_spinbox(LINK_EF, self.ui.spin_ef, just_update_value)

    def on_structure_changed(self):
        self.init_all_angle_spinbox()
        self.init_all_link_spinbox(True)

    def init_all_angle_spinbox(self):
        structure = self.ui.leg_widget.get_structure()
        self.ui.spin_alpha.blockSignals(True)
        self.ui.slider_alpha.setValue(round(structure.get_angle_degrees_value(ANGLE_ALPHA)))
        self.ui.spin_alpha.blockSignals(False)

        self.ui.spin_beta.blockSignals(True)
        self.ui.slider_beta.setValue(round(structure.get_angle_degrees_value(ANGLE_BETA)))
        self.ui.spin_beta.blockSignals(False)

        self.ui.spin_theta.blockSignals(True)
        self.ui.slider_theta.setValue(round(structure.get_angle_degrees_value(ANGLE_THETA)))
        self.ui.spin_theta.blockSignals(False)

    def init_link_spinbox(self, link_name, spinbox, just_update_value=False):
        spinbox.setValue(round(self.ui.leg_widget.get_structure().get_link_value(link_name)))

        if just_update_value:
            return

        spinbox.valueChanged['int'].connect(lambda v: self.on_link_changed(v, link_name))

    def on_degrees_changed(self, degrees, angle_name):
        print(f"{angle_name} changed to {degrees}")
        self.ui.leg_widget.update_angle_value(angle_name, degrees)

    def on_link_changed(self, value, link_name):
        print(f"{link_name} changed to {value}")
        self.ui.leg_widget.update_link_value(link_name, value)

    def on_mouse_tracking_event(self, x, y):
        self.ui.statusbar.showMessage("x: {: >6.1f}mm,   y: {: >6.1f}mm".format(x, y))

    def on_shape_finished(self, shape: Shape, args):
        print("shape finished: ", shape)

        if shape == Shape.LINE:
            dialog = ShapeLineDialog(self, args)
            # dialog = ShapeLineDialog(self, [[-32.0, 38.0], [36, 78]])
            rst = dialog.exec_()
            self.update_shape_draw(Shape.NONE)
            if rst != QDialog.Accepted:
                return

            start, end = dialog.get_values()
            print("start: ", start, "end: ", end)
            self.ui.leg_widget.trajectory_generate(LineTrajectory.TRACK_KEY, start, end)

    def update_shape_draw(self, shape):
        self.ui.leg_widget.set_shape_drawing(shape)

        # self.ui.btn_traj_line.setVisible(shape == Shape.NONE)
        # self.ui.btn_traj_triangle.setVisible(shape == Shape.NONE)
        # self.ui.btn_traj_rect.setVisible(shape == Shape.NONE)
        # self.ui.btn_traj_circle.setVisible(shape == Shape.NONE)

        # self.ui.tab_shape_desc.setVisible(shape != Shape.NONE)
        # self.ui.btn_traj_shape_exit.setVisible(shape != Shape.NONE)


    def draw_circle(self):
        center_point, radius = (0.0, 60.0), 15.0
        self.ui.leg_widget.trajectory_generate(CircleTrajectory.TRACK_KEY, center_point, radius)
        # center_point, radius = (11.0, 70.0), 4.0
        # self.ui.leg_widget.trajectory_generate(CircleTrajectory.TRACK_KEY, center_point, radius)
        # center_point, radius = (22.0, 68.5), 5.0
        # self.ui.leg_widget.trajectory_generate(CircleTrajectory.TRACK_KEY, center_point, radius)

    def draw_rect(self):
        start_point = (-28.0, 40.0)
        width = 56.0
        height = 40.0
        self.ui.leg_widget.trajectory_generate(RectTrajectory.TRACK_KEY, start_point, width, height)

    def draw_triangle(self):
        point1 = (20.0, 45.0)
        point2 = (10.0, 60.0)
        point3 = (30.0, 60.0)
        self.ui.leg_widget.trajectory_generate(TriangleTrajectory.TRACK_KEY, point1, point2, point3)
